package gitee.derumin.really_forge.helpers;

import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ExperienceOrbEntity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.EnchantedBookItem;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtList;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import net.minecraft.util.Util;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;

import java.util.ArrayList;
import java.util.Map;

public class ForgingResultHelper {

    private ItemStack output;
    private int expStored;
    private float maxProcedure;
    private float currentProcedure;
    private World world;
    private final BlockPos pos;

    public ForgingResultHelper(World world, BlockPos pos){
        this.output = ItemStack.EMPTY;
        this.expStored =  0;
        this.maxProcedure = 0;
        this.currentProcedure = 0;
        this.world = world;
        this.pos = pos;
    }

    public void writeNbt(NbtCompound nbt){
        NbtCompound outputNbt = new NbtCompound();
        nbt.put("output", this.output.writeNbt(outputNbt));
        nbt.putInt("expStored", this.expStored);
        nbt.putFloat("maxProcedure", this.maxProcedure);
        nbt.putFloat("currentProcedure", this.currentProcedure);
    }

    public void readNbt(NbtCompound nbt) {
        this.output = ItemStack.fromNbt(nbt.getCompound("output"));
        this.expStored = nbt.getInt("expStored");
        this.maxProcedure = nbt.getFloat("maxProcedure");
        this.currentProcedure = nbt.getFloat("currentProcedure");
    }

    public void setWorld(World world) {
        this.world = world;
    }

    public float getProgress(){
        if (this.maxProcedure <= 0){
            return 0;
        }
        return this.currentProcedure /this.maxProcedure;
    }

    public boolean tryForge(PlayerEntity player, DefaultedList<ItemStack> inventory, float power, boolean increasePunishment){
        if (this.output.isEmpty() && !this.updateOutput(inventory)){
            this.world.playSound((Entity) null, this.pos, SoundEvents.BLOCK_ANVIL_PLACE, SoundCategory.BLOCKS, (float) (0.8+Math.random()*0.1), (float) (1.6+Math.random()*0.4));
            this.remove();
            return false;
        }
        if (!player.isCreative()){
            if ((player.experienceLevel < (this.maxProcedure - this.currentProcedure)/(power-0.01))){
                this.world.playSound((Entity) null, this.pos, SoundEvents.BLOCK_ANVIL_PLACE, SoundCategory.BLOCKS, (float) (0.3+Math.random()*0.05), (float) (2.6+Math.random()*0.4));
                return false;
            }
            player.addExperienceLevels(-1);
            this.storeExp(player.getNextLevelExperience());
        }
        return this.tryProduceOutPut(power, increasePunishment, player.isCreative());
    }

    private boolean tryProduceOutPut(float power, boolean increasePunishment, boolean canImmediateProduce){
        this.currentProcedure += power;
        if (increasePunishment && !canImmediateProduce){
            this.output.setRepairCost(this.output.getRepairCost()+1);
        }
        if ((this.currentProcedure < this.maxProcedure) && !canImmediateProduce){
            this.world.playSound((Entity) null,this.pos, SoundEvents.BLOCK_ANVIL_USE, SoundCategory.BLOCKS, (float) (1.0+Math.random()*0.1), (float) (1.0+Math.random()*0.05));
            return false;
        }
        this.world.playSound((Entity) null,this.pos, SoundEvents.BLOCK_ANVIL_USE, SoundCategory.BLOCKS, (float) (1.0+Math.random()*0.1), (float) (0.8+Math.random()*0.05));
        ItemEntity itemEntity = new ItemEntity(
                this.world,
                this.pos.getX()+0.5,
                this.pos.getY()+1,
                this.pos.getZ()+0.5,
                this.output.copy());
        itemEntity.setToDefaultPickupDelay();
        this.world.spawnEntity(itemEntity);
        this.output = ItemStack.EMPTY;
        this.expStored = 0;
        this.maxProcedure = 0;
        this.currentProcedure = 0;
        return true;
    }

    private void storeExp(int exp){
        this.expStored += exp;
    }

    public void remove(){
        if (this.expStored > 0 && this.world instanceof ServerWorld serverWorld){
            ExperienceOrbEntity.spawn(
                    serverWorld,
                    new Vec3d(this.pos.getX()+0.5, this.pos.getY()+1, this.pos.getZ()+0.5),
                    this.expStored);
        }
        this.output = ItemStack.EMPTY;
        this.expStored =  0;
        this.currentProcedure = 0;
        this.maxProcedure = 0;
    }

    private boolean updateOutput(DefaultedList<ItemStack> inventory){
        ItemStack input1 = inventory.get(0);
        ItemStack input2 = inventory.get(1);
        ItemStack rawOutput = input1.copy();
        if (input2.isOf(Items.WRITTEN_BOOK) && input2.getNbt() !=null){
            NbtList list = input2.getNbt().getList("pages", NbtElement.STRING_TYPE);
            ArrayList<String> names = new ArrayList<>();
            for (NbtElement nbtElement : list) {
                String string = nbtElement.asString();
                if (string != null && !Util.isBlank(string) && !string.equals(rawOutput.getName().getString())){
                    names.add(string);
                }
            }
            if (names.isEmpty()){
                return false;
            }
            rawOutput.setCustomName(Text.Serializer.fromJson(names.get((int) (names.size()*Math.random()))));
            this.output = rawOutput;
            this.maxProcedure = 1;
            this.currentProcedure = 0;
            return true;
        }
        Map<Enchantment, Integer> map = EnchantmentHelper.get(rawOutput);
        Map<Enchantment, Integer> map1 = EnchantmentHelper.get(input1);
        Map<Enchantment, Integer> map2 = EnchantmentHelper.get(input2);
        boolean isBook = input2.isOf(Items.ENCHANTED_BOOK) && !EnchantedBookItem.getEnchantmentNbt(input2).isEmpty();
        int successCount = 0;
        int damageRepair = 0;
        int extraCost = 0;
        if (input1.isDamageable() && input1.getItem().canRepair(input1,input2)){
            damageRepair = Math.min(input1.getDamage(), input1.getMaxDamage() / 4);
            if (damageRepair <= 0) {
                return false;
            }
            extraCost += 1;
            successCount ++;
        }else if (!isBook){
            if (!input1.isOf(input2.getItem())){
                return false;
            }
            if (input1.isDamageable()){
                damageRepair = Math.min(input1.getDamage(), input2.getMaxDamage()*112/100-input2.getDamage());
                if (damageRepair <= 0 && map2.isEmpty()){
                    return false;
                }
                extraCost += 2;
                successCount ++;
            }
        }
        for (Enchantment enchantment : map2.keySet()) {
            if (enchantment == null) {
                continue;
            }
            boolean canAccept = enchantment.isAcceptableItem(rawOutput);
            if (rawOutput.isOf(Items.ENCHANTED_BOOK)){
                canAccept = true;
            }
            for (Enchantment enchantmentX : map1.keySet()){
                if (enchantmentX != null && enchantmentX != enchantment && !enchantment.canCombine(enchantmentX)){
                    canAccept = false;
                }
            }
            if (!canAccept){
                continue;
            }
            int level1 = map1.getOrDefault(enchantment,0);
            int level2 = map2.get(enchantment);
            level1 = level2 == level1? level1+1 : Math.max(level1, level2);
            level1 = Math.min(level1, enchantment.getMaxLevel());
            if (level1 == map1.getOrDefault(enchantment,0)){
                continue;
            }
            map.put(enchantment, level1);
            int enchantCost = switch (enchantment.getRarity()) {
                case COMMON -> 1;
                case UNCOMMON -> 2;
                case RARE -> 4;
                case VERY_RARE -> 8;
            };
            if (isBook){
                enchantCost = Math.max(1, enchantCost/2);
            }
            extraCost += enchantCost * level1;
            successCount ++;
        }
        if (successCount <= 0){
            return false;
        }
        int repairCost = input1.getRepairCost() + input2.getRepairCost();
        rawOutput.setRepairCost(Math.max(input1.getRepairCost(), input2.getRepairCost()));
        EnchantmentHelper.set(map,rawOutput);
        this.output = rawOutput;
        this.output.setDamage(input1.getDamage() - damageRepair);
        this.maxProcedure = extraCost + repairCost;
        this.currentProcedure = 0;
        return true;
    }
}
